package net.cyndeline.scalarlib.rldungeon.dgs.strategy.pointlessArea.algorithms.algorithmModules

import net.cyndeline.scalarlib.rldungeon.dgs.graph.{DGSFunctionality, Room}
import scalax.collection.GraphEdge.UnDiEdge
import scalax.collection.GraphPredef.OuterEdge
import net.cyndeline.scalarlib.rldungeon.dgs.strategy.help.{SuperGraphVertexReplacer, SuperGraphVertexReplacerInterface, CollapsedEdge, CollapsedNode}
import scalax.collection.immutable.Graph
import net.cyndeline.scalarlib.rlgraph.util.{EdgeCopyFactory, VertexReplacer}
import net.cyndeline.scalarlib.rldungeon.dgs.RewardProcessor
import com.escalatesoft.subcut.inject.{Injectable, BindingModule}
import net.cyndeline.scalarlib.rldungeon.dgs.strategy.pointlessArea.{PointlessNodeFinder, RepresentedVertexFinder}
import scala.util.Random
import scala.reflect.runtime.universe._
import scala.reflect._
import net.cyndeline.scalarlib.rldungeon.dgs.strategy.pointlessArea.help.{RandomRepresentedVertexFinder, MaximumDistancePointlessNode}

/**
 * Used for injection.
 */
trait RewardAppenderI {
  def addReward[VType <: Room[VType] : TypeTag : ClassTag, EType[X] <: UnDiEdge[X] with DGSFunctionality[X, EType] with OuterEdge[X, EType]]
               (collapsedGraph: Graph[CollapsedNode[VType], CollapsedEdge[VType]#Inner],
                regularGraph: Graph[VType, EType],
                edgeFactory: EdgeCopyFactory[VType, EType],
                rewardProcessor: RewardProcessor[VType, EType]
                ): Option[RewardResult[VType, EType]]
}

/**
 * Contains resulting data structures fom the reward appender.
 * @constructor Constructs a new reward result.
 * @param updatedCollapsedGraph The collapsed input graph with its vertices updated.
 * @param updatedRegularGraph The regular input graph with its vertices updated.
 * @param oldNode The node that received the reward.
 * @param updatedNodeWithReward The updated version of the old node with the reward.
 * @tparam VType Type of vertices in the graph.
 * @tparam EType Type of edge in the graph.
 */
final case class RewardResult[VType <: Room[VType] : TypeTag : ClassTag, EType[X] <: UnDiEdge[X] with DGSFunctionality[X, EType] with OuterEdge[X, EType]]
                             (updatedCollapsedGraph: Graph[CollapsedNode[VType], CollapsedEdge[VType]#Inner],
                              updatedRegularGraph: Graph[VType, EType],
                              oldNode: CollapsedNode[VType],
                              updatedNodeWithReward: CollapsedNode[VType])

/**
 * Adds rewards to vertices and updates their graphs.
 *
 * @constructor Creates a new reward appender module.
 */
class RewardAppender(random: Random)(implicit val bindingModule: BindingModule)
  extends Injectable
  with RewardAppenderI {

  private val superNodeReplacer = injectOptional[SuperGraphVertexReplacerInterface] getOrElse { new SuperGraphVertexReplacer() }

  /**
   * Adds a reward to a vertex inside a node, and updates a collapsed graph with the new node, as well as the regular
   * graph with the new vertex.
   * @param collapsedGraph A collapsed graph that may contain a pointless node.
   * @param regularGraph Graph containing the vertices represented by the collapsed graph.
   * @return A collapsed graph updated with a node containing the vertex that was selected to receive the reward,
   *         a regular graph updated with the vertex, and a collapsed node that has been updated with the vertex.
   *         Or None if no pointless node capable of carrying rewards was found.
   */
  def addReward[VType <: Room[VType] : TypeTag : ClassTag, EType[X] <: UnDiEdge[X] with DGSFunctionality[X, EType] with OuterEdge[X, EType]]
               (collapsedGraph: Graph[CollapsedNode[VType], CollapsedEdge[VType]#Inner],
                regularGraph: Graph[VType, EType],
                edgeFactory: EdgeCopyFactory[VType, EType],
                rewardProcessor: RewardProcessor[VType, EType]
                ): Option[RewardResult[VType, EType]] = {

    /* Finds a candidate node for activators to be placed on. */
    val pointlessNodeFinder = injectOptional[PointlessNodeFinder[VType]] getOrElse { new MaximumDistancePointlessNode[VType](random) }

    val vertexFinder = injectOptional[RepresentedVertexFinder[VType]] getOrElse { new RandomRepresentedVertexFinder[VType](random) }
    val vertexReplacer = new VertexReplacer(edgeFactory)

    val node = pointlessNodeFinder.findRewardNode(collapsedGraph)

    if (node.isDefined) {
      val vertexToReceiveReward = vertexFinder.findVertexThatHoldsRewards(node.get).getOrElse {
        throw new Error("The node " + node + " was selected to receive a reward, but none of its vertices can carry any.")
      }
      val vertexWithReward = rewardProcessor.addReward(vertexToReceiveReward, regularGraph)
      val replacementResult = superNodeReplacer.replaceVertexSetUsingNode(Map(vertexToReceiveReward -> vertexWithReward), node.get, collapsedGraph)
      val updatedCollapsedGraph = replacementResult._1
      val updatedNode = replacementResult._2

      val updatedRegularGraph = vertexReplacer.replace(vertexToReceiveReward, vertexWithReward, regularGraph)

      Option(RewardResult(updatedCollapsedGraph, updatedRegularGraph, node.get, updatedNode))
    } else {
      None
    }
  }
}
